package org.example.test;

import cn.hutool.core.lang.Assert;
import com.poizon.infrastructure.idem.core.domain.entity.IdemEntity;
import com.poizon.infrastructure.idem.core.domain.value.enums.IdemReqType;
import com.poizon.infrastructure.idem.core.domain.value.exception.IdempotentDbStoreException;
import com.poizon.infrastructure.idem.core.util.impl.IdemUtil;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

public class IdemUtilTest {

    public static final String RES = "res";
    public static final IdemUtil spy = Mockito.spy(new IdemUtil(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1))));
    public static final MockedStatic<IdemEntity> IDEM_ENTITY_MOCKED_STATIC = mockStatic(IdemEntity.class);

    @Test
    public void executeIdemImpl_幂等数据不存在() throws Exception {
        // 创建mock对象实例
        // 当前幂等对象不存在
        IDEM_ENTITY_MOCKED_STATIC.when(() -> IdemEntity.getIdemDto(anyString())).thenReturn(null);
        String res = "幂等响应";
        doReturn(res).when(spy).idemObjNotExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class));
        // 业务正常执行
        Object o1 = spy.executeIdem("幂等key", () -> {
            return res;
        });
        Assert.equals(res, o1.toString());

        // 业务执行异常
        String 业务执行异常 = "业务执行异常";
        doThrow(new RuntimeException(业务执行异常)).when(spy).idemObjNotExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class));
        try {
            spy.executeIdem("幂等key", () -> {
                return res;
            });
        } catch (Exception e) {
            Assert.equals(业务执行异常, e.getMessage());
        }

        // 幂等底层存储异常
        doThrow(new IdempotentDbStoreException("幂等存储异常")).when(spy).idemObjNotExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class));
        try {
            spy.executeIdem("幂等key", () -> {
                return res;
            });
        } catch (Exception e) {
            Assert.isTrue(e instanceof IdempotentDbStoreException);
        }
    }

    @Test
    public void executeIdemImpl_幂等数据存在() throws Exception {
        // 创建mock对象实例
        MockedStatic<IdemEntity> idemEntityMockedStatic = mockStatic(IdemEntity.class);
        // 当前幂等对象存在
        idemEntityMockedStatic.when(() -> IdemEntity.getIdemDto(anyString())).thenReturn(new IdemEntity());

        doReturn(RES).when(spy).idemObjHasExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class), any(IdemEntity.class));

        Object o = spy.executeIdem("幂等key", () -> {
            return RES;
        });
        Assert.equals(RES, o.toString());

        // 业务执行异常
        String 业务执行异常 = "业务执行异常";
        doThrow(new RuntimeException(业务执行异常)).when(spy).idemObjHasExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class), any(IdemEntity.class));
        try {
            spy.executeIdem("幂等key", () -> {
                return RES;
            });
        } catch (Exception e) {
            Assert.equals(业务执行异常, e.getMessage());
        }

        // 幂等底层存储异常
        doThrow(new IdempotentDbStoreException("幂等存储异常")).when(spy).idemObjHasExist(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class), any(IdemEntity.class));
        try {
            spy.executeIdem("幂等key", () -> {
                return RES;
            });
        } catch (Exception e) {
            Assert.isTrue(e instanceof IdempotentDbStoreException);
        }
    }

    /**
     * 幂等对象已经存在
     */
    @Test
    public void idemObjHasExist_() throws Exception {
        IdemEntity idemEntity = new IdemEntity();
        // 响应已经落库
        idemEntity.setStatus(true);
        idemEntity.setReqId(RES);
        doReturn(RES).when(spy).idemObjHasExistAndHasResp(any());

        // resp已经落库
        Object o = spy.idemObjHasExist("test", () -> {
            return RES;
        }, 1000L, TimeUnit.MILLISECONDS, "HTTP", new AtomicInteger(1), idemEntity);
        Assert.equals(RES, o.toString());

        // resp未落库,重试获取响应

        idemEntity.setStatus(false);

        // 获取响应成功
        doReturn(RES).when(spy).idemObjHasExistAndNoResp(any(), any(), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class));
        Object o1 = spy.idemObjHasExist("test", () -> {
            return RES;
        }, 1000L, TimeUnit.MILLISECONDS, "HTTP", new AtomicInteger(1), idemEntity);
        Assert.equals(RES, o1.toString());

        // 获取响应失败
        String 获取响应失败 = "获取响应失败";
        doThrow(new RuntimeException(获取响应失败)).when(spy).idemObjHasExistAndNoResp(any(), any(), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class));
        try {
            spy.idemObjHasExist("test", () -> {
                return RES;
            }, 1000L, TimeUnit.MILLISECONDS, "HTTP", new AtomicInteger(1), idemEntity);
        } catch (Exception e) {
            Assert.equals(获取响应失败, e.getMessage());
        }

    }

    /**
     * 幂等对象不存在
     */
    @Test
    public void idemObjNotExist() throws Exception {
        IdemEntity mock = mock(IdemEntity.class);
        IDEM_ENTITY_MOCKED_STATIC.when(() -> IdemEntity.buildCreateIdemDto(anyString(), any(IdemReqType.class))).thenReturn(mock);
        when(mock.setIdemKey(anyString(), any(IdemEntity.class), anyLong(), any(TimeUnit.class))).thenReturn(true);

        // 设置成功  设置失败
        doReturn(RES).when(spy).idemObjNotExistAndNoConcurrencyViolation(anyString(), any(IdemEntity.class), any(Supplier.class), anyLong(), any(TimeUnit.class));

        Object o = spy.idemObjNotExist("test", () -> {
            return RES;
        }, 1000L, TimeUnit.MILLISECONDS, "HTTP", new AtomicInteger(1));
        Assert.equals(RES, o.toString());

        when(mock.setIdemKey(anyString(), any(IdemEntity.class), anyLong(), any(TimeUnit.class))).thenReturn(false);
        doReturn(RES).when(spy).idemObjNotExistAndHasConcurrencyViolation(anyString(), any(Supplier.class), anyLong(), any(TimeUnit.class), anyString(), any(AtomicInteger.class), anyString());

        o = spy.idemObjNotExist("test", () -> {
            return RES;
        }, 1000L, TimeUnit.MILLISECONDS, "HTTP", new AtomicInteger(1));
        Assert.equals(RES, o.toString());

    }

    /**
     * 幂等响应刷新入库
     */
    @Test
    public void idemObjNotExistAndNoConcurrencyViolation_业务执行异常() throws Exception {
        try {
            IdemEntity mock = mock(IdemEntity.class);
            // 业务执行异常,回退幂等数据成功
            spy.idemObjNotExistAndNoConcurrencyViolation("test", mock, () -> {
                throw new RuntimeException("业务执行异常");
            }, 1000L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            Assert.equals("业务执行异常", e.getMessage());
        }
        try {
            IdemEntity mock = mock(IdemEntity.class);
            doThrow(new IdempotentDbStoreException("db异常")).when(mock).delete(anyString());
            // 业务执行异常,回退米的呢数据失败
            spy.idemObjNotExistAndNoConcurrencyViolation("test", mock, () -> {
                throw new RuntimeException("业务执行异常");
            }, 1000L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            Assert.equals("业务执行异常", e.getMessage());
        }
    }

    @Test
    public void idemObjNotExistAndNoConcurrencyViolation_业务执行正常() throws Exception {
        IdemEntity mock = mock(IdemEntity.class);
        // 业务执行异常,回退幂等数据成功
        doNothing().when(spy).flushIdemResAsync(anyString(), any(IdemEntity.class), any(), anyLong(), any(TimeUnit.class));
        Object o = spy.idemObjNotExistAndNoConcurrencyViolation("test", mock, () -> {
            return RES;
        }, 1000L, TimeUnit.MILLISECONDS);
        Assert.equals(RES, o.toString());
    }

    /**
     * 异步刷新幂等响应入库测试
     */
    @Test
    public void flushIdemResAsync() throws Exception {
        // mock idementity
        IdemEntity mock = mock(IdemEntity.class);
        doNothing().when(mock).updateIdemResp(any(), anyLong(), any(TimeUnit.class), anyString());

        // 更新方法正常执行
        spy.flushIdemResAsync(
                "test",
                mock,
                RES,
                1000L,
                TimeUnit.MILLISECONDS
        );

        // 线程池更新方法异常
        doThrow(new IdempotentDbStoreException("更新方法异常")).when(mock).updateIdemResp(any(), anyLong(), any(TimeUnit.class), anyString());
        spy.flushIdemResAsync(
                "test",
                mock,
                RES,
                1000L,
                TimeUnit.MILLISECONDS
        );
    }

    /**
     * 重试测试
     */
    @Test
    public void retry() throws Exception {
         try {
             Object retry = spy.retry(
                     "test",
                     1000L,
                     "HTTP",
                     () -> {
                         throw new RuntimeException("重试异常");
                     },
                     new AtomicInteger(0),
                     TimeUnit.MILLISECONDS
             );

         } catch (Exception e) {
             Assert.isTrue(e instanceof IdempotentDbStoreException);
          }

    }

}
